' searchplugin.bmx

Strict

Import "../maxtk/maxtk.bmx"

Type TCErrIO Extends TStream

	Method Flush()
		fflush_ stderr_
	End Method

	Method Read( buf:Byte Ptr,count )
		Return fread_( buf,1,count,stderr_ )
	End Method

	Method Write( buf:Byte Ptr,count )
		Return fwrite_( buf,1,count,stderr_ )
	End Method

End Type

Global ErrStream:TStream=TTextStream.Create( New TCErrIO,TTextStream.UTF8 )

Function SearchFile(path$,text$)
	Local	file:TStream
	Local	linenumber,findpos,line$
	Local	foundinfile
	file=ReadFile(path)
	If Not file RuntimeError "Failed to read file "+path
	While Not Eof(file)
		line=ReadLine(file).toLower()
		linenumber:+1
		findpos=line.find(text)
		If findpos<>-1 
			If Not foundinfile
				ErrStream.WriteLine "Found in "+path
				foundinfile=True
			EndIf
			ErrStream.WriteLine "["+linenumber+":"+findpos+"]"+line.trim()			'+" ["+findpos+":"+linenumber+"]"
			ErrStream.Flush
		EndIf
	Wend
	CloseFile file
End Function

Function SearchDir(path$,text$,filter$)
	Local dir=ReadDir(path)
	If Not dir RuntimeError "Failed to read directory "+path
	text=text.toLower()
	filter=filter.toLower()	
	Repeat
		Local f$=NextFile(dir)
		If f$="" Exit
		If f[..1]="." Continue
		f$=path+"/"+f$
		Select FileType(f$)
			Case FILETYPE_FILE
				Local ext$=ExtractExt(f$).toLower()
				If filter And (ext="" Or filter.Find(ext$)=-1) Continue
				SearchFile f$,text
			Case FILETYPE_DIR
				SearchDir f$,text,filter
		End Select
	Forever
	CloseDir dir
End Function

' global entry point for reentrant createprocess call below

If AppArgs.length>3 And AppArgs[1]="search"
	Local filter$
	If AppArgs.length>4 filter$=AppArgs[4]
	ErrStream.WriteLine "Searching "+AppArgs[2]+" for "+AppArgs[3]+" with "+filter
	SearchDir AppArgs[2],AppArgs[3],filter
	End ' yes, i said reentrant...
EndIf

Type TSearchRequester Extends TRequester
	Field searchplugin:TSearchPlugin
	Field	findbox:TGadget
	Field	typebox:TGadget
	Field	pathbox:TGadget
	Field	pathbutton:TGadget

	Method DoSearch()
		Local path$,text$,filter$
		path=pathbox.GetText()
		text=findbox.GetText()		
		filter=GadgetItemText(typebox,SelectedGadgetItem(typebox))
		Local f=Instr(filter,":")
		If f filter=filter[f..]
		If path And text searchplugin.Search(path,text,filter)
	End Method

	Method Show()
		Local current=SelectedGadgetItem(typebox)
		If current=-1 current=0
		ClearGadgetItems typebox
		For Local plugin:TPlugin=EachIn host.pluginlist
			If plugin.filters
				AddGadgetItem typebox,plugin.FileFilter()
			EndIf
		Next	
		SelectGadgetItem typebox,current
		Super.Show()
		ActivateGadget findbox
	End Method

	Method OnEvent()
		Local	id,data,index
		id=EventID()
		data=EventData()
		Select EventSource()
			Case window
				If EventID()=EVENT_WINDOWCLOSE
					Hide
				EndIf
			Case ok
				DoSearch
				Hide				
			Case cancel
				Hide
			Case pathbutton
				SetGadgetText pathbox,RequestDir("Select a Search Path",pathbox.GetText())
		End Select
	End Method

	Function Create:TSearchRequester(plugin:TSearchPlugin)
		Local	search:TSearchRequester
		search=New TSearchRequester		
		search.searchplugin=plugin
		search.initrequester(plugin.host,"Find In Files",400,256)
		CreateLabel("Find What",6,18,80,24,search.window)
		search.findbox=CreateTextField(88,14,240,24,search.window)
		CreateLabel("File Types",6,48,80,24,search.window)
		search.typebox=CreateComboBox(88,44,240,24,search.window)
		CreateLabel("Search Path",6,78,80,24,search.window)
		search.pathbox=CreateTextField(88,74,240,24,search.window)		
		SetGadgetText search.pathbox,CurrentDir()
		search.pathbutton=CreateButton("..",340,72,48,24,search.window)
		Return search
	End Function

End Type

Type TSearchNode Extends TDataNode
	Field owner:TSearchPlugin
	Field file$,line,column

	Method Invoke(command$,argument:Object=Null)
		Select command
			Case "ACTIVATE"
				owner.host.DebugSource file,line,column
		End Select
	End Method

	Method SetDetails(o:TSearchPlugin,n$,f$,l,c)
		SetName n
		owner=o
		file=f
		line=l
		column=c
		SetAction(owner,"ACTIVATE",Self)
	End Method
End Type

Type TSearchPlugin Extends TPlugin

	Field	searchcommand
	Field	searchrequester:TRequester
	Field	searchroot:TDataNode
	Field	term:TDataNode
	Field	file:TDataNode
	Field	firsthit:TSearchNode
	Field 	isopen

	Method Initialize(owner:THost)
		host=owner
		SetName("Search")
		searchrequester=New TSearchRequester.Create(Self)		
		searchcommand=AddCommand("&Tools","Search",KEY_F,MODIFIER_CONTROL|MODIFIER_SHIFT)			
		searchroot=TDataNode.Create("Search")
	End Method

	Method Search(path$,text$,filter$)
		Local cmd$=AppArgs[0]+" search "+quote(path)+" "+quote(text)
		If filter cmd:+" "+filter
		firsthit=Null
		host.Execute Self,cmd
	End Method
	
	Method Process$(err$)
		Local hit:TSearchNode		
		If err="" ShowResults;Return		
		If err[..10]="Searching "
			term=TDataNode.Create(err[10..])
			term.Open
			searchroot.freekids
			searchroot.append term
		EndIf
		If err[..9]="Found in "
			file=TDataNode.Create(err[9..])
'			file.Open
			term.append file
		EndIf
		If err[..1]="["
			Local p=err.find("]")
			Local q=err.find(":")
			Local line,pos
			If p=-1 Return err
			If q<p pos=Int(err[q+1..p]) Else q=p
			line=Int(err[1..q])
			hit=New TSearchNode
			hit.SetDetails Self,err,file.name,line,pos
			file.append hit
			If Not firsthit firsthit=hit
		EndIf		
	End Method

	Method ShowResults()
		If Not isopen
			searchroot.Open
			host.AddNode searchroot
			isopen=True
		EndIf
		searchroot.Refresh
		If firsthit firsthit.HighLight(0)
		host.SelectPanel host.activepanel
	End Method

	Method Invoke(command$,argument:Object=Null)
		If command=searchcommand
			searchrequester.Show
		EndIf
	End Method
		
	Method Open:Object(path$)
	End Method

End Type
